// Copyright (c) 2021 Huawei Device Co., Ltd.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

% def get_node_kind(mnemonic)
%   return "#{mnemonic.gsub('.', '_').upcase}"
% end

// Autogenerated file -- DO NOT EDIT!
import { PandaGen } from "./pandagen";
import * as ts from "typescript";
import * as jshelpers from "./jshelpers";
import {
  DebugInfo,
  DebugPosInfo,
  NodeKind
} from "./debuginfo";
import { LOGE } from "./log"

export enum IRNodeKind {
  VREG,
  IMM,
  LABEL,
  VIRTUALINS_DYN,
  DEFINE_GLOBAL_VAR,
% Panda::instructions.group_by(&:mnemonic).each do |mnemonic, group|
  <%= get_node_kind(mnemonic) %>,
% end
}

export function getInstructionSize(opcode: IRNodeKind) {
  switch(opcode) {
% Panda::instructions.each do |insn|
% node_kind = get_node_kind(insn.mnemonic)
% kind = "IRNodeKind." + node_kind
    case <%= kind %>:
      return <%= insn.format.size %>;
% end
    default:
      // LOGE("getInstructionSize: Unknown opcode:" + opcode);
      return 0;
  }
}

export enum ResultType {
  None,
  Unknown,
  Int,
  Long,
  Float,
  Obj,
  Boolean
}

export enum ResultDst {
  None,
  Acc,
  VReg
}

export enum BuiltIns {
  NaN,
  Infinity,
  globalThis,
  undefined,
  Boolean,
  Number,
  String,
  BigInt,
  Symbol,
  Null,
  Object,
  Function,
  Global,
  True,
  False,
  LexEnv,
  MAX_BUILTIN,
}

export type OperandType = VReg | Imm | Label | string | number

export enum OperandKind {
  // the least significant bit indicates vreg
  // the second bit indicates src or dst
  SrcVReg, DstVReg, SrcDstVReg, Imm, Id, StringId, Label
}

export namespace OperandKind {
  function isVReg(kind: OperandKind): boolean {
    return kind === OperandKind.SrcVReg || kind === OperandKind.DstVReg || kind === OperandKind.SrcDstVReg;
  }
}

export class FormatItem {
  constructor(
    readonly kind: OperandKind,
    readonly bitwidth: number,
  ) {}
}

export type Format = FormatItem[]

export abstract class IRNode {
  private node: ts.Node | NodeKind = NodeKind.Normal;
  constructor(
    readonly kind: IRNodeKind,
    readonly mnemonic: string,
    readonly operands: OperandType[],
    readonly formats: Format[]
  ) {}
  // for debuginfo
  public debugPosInfo: DebugPosInfo = new DebugPosInfo();

  abstract resultType(): ResultType;
  abstract resultIn(): ResultDst;

  toString(): string {
    let out = this.mnemonic + "\t";
    if (this.mnemonic.length < 8) {
      out += "\t";
    }

    this.operands.forEach((element) => {
      out = out + element.toString() + ", ";
    });

    return out;
  }

  setNode(node: ts.Node | NodeKind) {
    this.node = node;
  }

  getNodeName() {
    if (this.node != NodeKind.Invalid &&
        this.node != NodeKind.FirstNodeOfFunction &&
        this.node != NodeKind.Normal) {
      return ts.SyntaxKind[(<ts.Node>this.node).kind];
    }

    return "undefined";
  }
}

export abstract class Intrinsic extends IRNode {
  slotSize: number = 0;
  constructor(
    readonly kind: IRNodeKind,
    readonly mnemonic: string,
    readonly operands: OperandType[],
    readonly formats: Format[]
  ) {
    super(kind, mnemonic, operands, formats);
  }

  toString(): string {
    return super.toString() + " [i]";
  }

  hasIC(): boolean {
    return this.slotSize > 0;
  }

  updateICOffset(base: number): number {return 0};

  validateIC(offset: number, slotSize: number): number {
    let end = offset + slotSize;
    if (end > 0xFFFF) {
      return 0xFFFF;
    } else {
      return offset;
    }
  }
}

export class VReg {
  private static global_id = 0;
  private typeIndex: number = 0;
  private variableName: string = "";
  readonly id: number; // used for debug purpose to distinguish one instance from another
  num: number = -1;
  
  // for debug purposes
  private stacktrace: (undefined | string);
  
  toString(): string {
    if (this.num != -1) {
      return "V" + this.num;
    } else {
      return "L" + this.id;
    }
  }

  constructor() {
    this.id = VReg.global_id++;
    
    // for debug purposes
    this.setStackTrace(null);
  }
  
  // for debug purposes
  getStackTrace(): (undefined | string) {
    return this.stacktrace;
  }
  
  setStackTrace(stack?: null): void {
    if (stack === undefined) {
      let error = new Error();
      let trace = error.stack;
      
      this.stacktrace = trace;
      return;
    }
    
    if (stack === null) {
      this.stacktrace = undefined;
      return;
    }
  }

  getTypeIndex() {
    return this.typeIndex;
  }

  setTypeIndex(typeIndex: number) {
    this.typeIndex = typeIndex;
  }

  getVariableName() {
    return this.variableName;
  }

  setVariableName(variableName: string) {
    this.variableName = variableName;
  }
}

export class Imm extends IRNode {
  private type: ResultType;
  readonly value: number;

  constructor(type: ResultType, value: number) {
    super(IRNodeKind.IMM, "", [], []);
    this.type = type;
    this.value = value;
  }

  static zero(): Imm {
    return new Imm(ResultType.Int, 0);
  }

  static one(): Imm {
    return new Imm(ResultType.Int, 1);
  }

  resultType(): ResultType {
    return this.type;
  }

  resultIn(): ResultDst {
    return ResultDst.None;
  }

  toString(): string {
    return "#" + this.value;
  }
}

export class Label extends IRNode {
  private static global_id = 0;
  readonly id: number; // used for debug purpose to distinguish one instance from another

  constructor() {
    super(IRNodeKind.LABEL, "", [], []);
    this.id = Label.global_id++;
  }

  resultType(): ResultType {
    return ResultType.None;
  }

  resultIn(): ResultDst {
    return ResultDst.None;
  }

  toString(): string {
    return "LABEL_" + this.id;
  }
}

export class DebugInsPlaceHolder extends IRNode {
  constructor() {
    super(IRNodeKind.VIRTUALINS_DYN, "", [], []);
  }

  resultType(): ResultType {
    return ResultType.None;
  }

  resultIn(): ResultDst {
    return ResultDst.None;
  }
}

% def insn2node(insn)
%   mnemonic = insn.mnemonic.split('.')
%   return mnemonic.map{|el| el == '64' ? 'Wide' : el.capitalize}.join()
% end
%
% def get_result_type(insn)
%   if insn.mnemonic.start_with? "call"
%     return "ResultType.Unknown"
%   end
%   case insn.dtype
%   when 'i32'
%     return "ResultType.Int"
%   when 'i64', 'b64'
%     return "ResultType.Long"
%   when 'f64'
%     return "ResultType.Float"
%   when 'obj'
%     return "ResultType.Obj"
%   else
%     return "ResultType.None"
%   end
% end
%
% def get_result_dst(insn)
%   if insn.properties.include? "acc_write"
%     return "ResultDst.Acc"
%   end
%   dst_operands = insn.operands.select { |op| op.dst? }
%   if dst_operands.length > 0 and dst_operands[0].reg?
%     return "ResultDst.VReg"
%   end
%   return "ResultDst.None"
% end
%
% def is_VReg(name)
%     if name ==  :v
%        return true
%     end
% end
% 
% def is_Acc(name)
%    if name ==  :acc
%        return true
%     end
% end
% 
% def is_Imm(name)
%    if name ==  :imm
%        return true
%     end
% end
% 
% def is_Id(name)
%     if %i[method_id type_id field_id string_id literalarray_id callsite_id].include?(name)
%        return true
%     end
% end
%
% def is_Call(insn)
%   if insn.mnemonic.start_with? "call"
%      return true
%   end
%   return false
% end
%
% def is_CallRange(insn)
%   if insn.mnemonic == "call.range" or insn.mnemonic == "calli.dyn.range" or insn.mnemonic == "ecma.callirangedyn" or insn.mnemonic == "ecma.callithisrangedyn" or insn.mnemonic == "ecma.createobjectwithexcludedkeys" or insn.mnemonic == "ecma.newobjdynrange"
%      return true
%   end
%   return false
% end
%
% def get_operand_type(name, insn)
%   if is_VReg(name)
%     return "VReg"
%   elsif is_Imm(name)
%     is_jump = insn.properties.include? 'jump'
%     return is_jump ? "Label" : "Imm"
%   elsif is_Id(name)
%     return "string"
%   else
%     return nil
%   end
% end
%
% def get_operands(sig)
%     return [] unless sig.include? ' '
%     _, operands = sig.match(/(\S+) (.+)/).captures
%     operands = operands.split(', ')
%     end
%     
% def get_ctor_args(insn)
%     operands = get_operands(insn.sig)
%     ops = Array.new
%     ctor_args = Array.new
%     operands.map do |operand|
%     operand_parts = operand.split(':')
%       case operand_parts.size
%       when 1
%         name = operand_parts[0]
%       when 2
%         name, _ = operand_parts
%       when 3
%         name, _, _ = operand_parts
%       else
%         raise 'Unexpected operand string'
%       end 
%       ops.push(name)
%       name_tmp = name.to_s.gsub(/[0-9]/, '').to_sym
%       type = get_operand_type(name_tmp,insn)
%       if is_Call(insn) and !is_CallRange(insn) and is_VReg(name_tmp) and !insn.mnemonic.include?('acc')
%          ctor_args.push("#{name}?: #{type}")
%       else
%          ctor_args.push("#{name}: #{type}")
%       end
%     end
%     return ops,ctor_args
% end
%
% def get_operand_kind(op, insn)
%   if op.reg?
%     if op.src? and op.dst?
%       return "OperandKind.SrcDstVReg"
%     elsif op.src?
%       return "OperandKind.SrcVReg"
%     elsif op.dst?
%       return "OperandKind.DstVReg"
%     end
%     return nil
%   elsif op.imm?
%     is_jump = insn.properties.include? 'jump'
%     return is_jump ? "OperandKind.Label" : "OperandKind.Imm"
%  elsif op.id?
%    is_string_id = insn.properties.include? 'string_id'
%    return is_string_id ? "OperandKind.StringId" : "OperandKind.Id"
%  else
%    return nil
%  end
% end
%
% def make_format(fmt, insn)
%   operands = fmt.operands.map {|op| "new FormatItem(#{get_operand_kind(op, insn)}, #{op.width})"}
%   return "[" + operands.join(", ") + "]"
% end
%
% def get_parent_type(insn)
%  if insn.prefix != nil and insn.prefix.name == "ecma"
%     return "Intrinsic"
%  else
%     return "IRNode"
%  end
% end
% Panda::instructions.group_by(&:mnemonic).each do |mnemonic, group|
% insn = group.first
% jump = insn.properties.include? 'jump'
% node_kind = get_node_kind(mnemonic)
% kind = "IRNodeKind." + node_kind
% ops_list,ctor_arg_list = get_ctor_args(insn)
% ctor_args = ctor_arg_list.map {|arg| "#{arg}"}.join(", ")
% ops = ops_list.map { |op| "#{op}"}.join(", ")
% formats = group.map {|i| make_format(i,insn) }
% result_type = get_result_type(insn)
% result_dst = get_result_dst(insn);
% is_call_op = is_Call(insn)
% is_callrange_op = is_CallRange(insn)
% parent_type = get_parent_type(insn)
export class <%= insn2node(insn) %> extends <%=parent_type %> {
% if is_callrange_op
%   op_last = ops_list.pop
%   ops_length = ops_list.size
  constructor(<%= ctor_arg_list.join(", ")%>[]) {
    var ctors = [<%=ops_list.join(", ")%>, ...<%=op_last %>]
    var operands:OperandType[] = [<%=ops_list.join(", ")%>]
%   for i in 1..ops_length do
    ctors.shift()
%   end
    while (!!(ctors && ctors.length)){
      let ctor = ctors.shift()
      if (ctor != undefined) {
        operands.push(ctor)
      }
    }
% elsif insn2node(insn) == "BuiltinR2i"
  constructor(<%= ctor_arg_list[0] %>, <%= ctor_arg_list[1] %>, <%= ctor_arg_list[2] %>[]) {
    var operands:OperandType[] = [<%=ops_list[0] %>, <%=ops_list[1] %>, ...<%=ops_list[2] %>]
% else
  constructor(<%= ctor_args %>) {
% if is_call_op and ops.length() != 0
    var ctors = [<%= ops %>]
    var operands:OperandType[] = [<%=ops_list[0] %>]
    ctors.shift()
    while (!!(ctors && ctors.length)){
      let ctor = ctors.shift()
      if (ctor != undefined) {
        operands.push(ctor)
      }
    }
% end
% end
    super(
      <%= kind %>,
      "<%= insn.mnemonic %>",
% if is_call_op or is_callrange_op or insn2node(insn) == "BuiltinR2i"
      operands,
% else
      [<%= ops %>],
% end
      [
        <%= formats.join(",\n        ") %>
      ]
    );
  }
  resultType(): ResultType {
    return <%= result_type %>;
  }
  resultIn(): ResultDst {
    return <%= result_dst %>;
  }
% if jump
% target_index = insn.operands.index {|op| op.imm? }
  getTarget(): Label {
    return <Label> this.operands[<%= target_index %>];
  }
% end
}
% end
